<?php

namespace esp\core;

class Debug
{
    private $prevTime;
    private $memory;
    private $_run;
    private $_star;
    private $_time;
    private $_value;
    private $_print_format = '% 9.3f';
    private $_log_path = 'cache/debug';
    private $_showHtml = true;
    private $_showPost = true;
    private $_showServer = false;

    private $_node = [];
    private $_node_len = 0;
    private $_mysql = [];

    public function __construct(Request $request, Response $response, array $conf)
    {
        $this->_star = defined('_STAR') ? _STAR : [microtime(true), memory_get_usage()];
        $this->_run = boolval($conf['autorun'] ?? false);
        $this->_time = time();
        $this->prevTime = microtime(true) - $this->_star[0];
        $this->memory = memory_get_usage();
        $time = sprintf($this->_print_format, $this->prevTime * 1000);
        $memo = sprintf($this->_print_format, ($this->memory - $this->_star[1]) / 1024);
        $now = sprintf($this->_print_format, ($this->memory) / 1024);
        $this->_node[0] = ['t' => $time, 'm' => $memo, 'n' => $now, 'g' => ''];
        $this->prevTime = microtime(true);
        $this->relay('START:__construct', []);

        if (!boolval($conf['mysql'] ?? 0)) $this->_mysql = null;
        if (!boolval($conf['html'] ?? 0)) $this->_showHtml = false;
        if (!boolval($conf['post'] ?? 0)) $this->_showPost = false;
        if (boolval($conf['server'] ?? 0)) $this->_showServer = true;

        //将最后保存数据部分注册为关门动作
        register_shutdown_function(function () use ($request, $response) {
            $this->save_logs($request, $response);
        });
    }

    /**
     * 保存记录到的数据
     */
    private function save_logs(Request $request, Response $response)
    {
        if (empty($this->_node) or $this->_run === false) return;
        $this->relay('END:save_logs', []);
        $filename = $this->filename();
        $method = $request->getMethod();
        $data = [];
        $data[] = "## 请求数据\n";
        $data[] = " - METHOD:\t\t{$method}\n";
        $data[] = " - GET_URL:\t\t" . (defined('_URL') ? _URL : '') . "\n";
        $data[] = " - SERV_IP:\t\t" . ($_SERVER['SERVER_ADDR'] ?? '') . "\n";
        $data[] = " - USER_IP:\t\t" . ($_SERVER['REMOTE_ADDR'] ?? '') . "\n";
        $data[] = " - REAL_IP:\t\t" . ($_SERVER['X-REAL-IP'] ?? '') . "\n";
        $data[] = " - DATETIME:\t" . date('Y-m-d H:i:s', $this->_time) . "\n";
        $data[] = " - AGENT:\t\t" . ($_SERVER['HTTP_USER_AGENT'] ?? '') . "\n";
//        $data[] = " - UN_ID:\t\t" . (Client::id()) . "\n";
        $data[] = " - Router:\t\t/{$request->module}/{$request->controller}/{$request->action}\t({$request->route})\n";

        //一些路由结果，路由结果参数
        $Params = implode(',', $request->getParams());
        $data[] = " - Params:\t\t({$Params})\n";
        if (!empty($this->_value)) {
            foreach ($this->_value as $k => &$v) {
                $data[] = "- {$k}\t{$v}\n";
            }
        }

        $data[] = "\n## 程序执行顺序：\n```\n";
        $data[] = "\t\tuTime\tuMem\t\ttMem\t\n";
        $data[] = "  {$this->_node[0]['t']}\t{$this->_node[0]['m']}\t{$this->_node[0]['n']}\t{$this->_node[0]['g']}\n";
        unset($this->_node[0]);
        $data[] = "" . (str_repeat('-', 100)) . "\n";
        //具体监控点
        $len = min($this->_node_len + 3, 100);
        foreach ($this->_node as $i => &$row) {
            $data[] = "  {$row['t']}\t{$row['m']}\t{$row['n']}\t" . sprintf("%-{$len}s", $row['g']) . "\t{$row['f']}\n";
        }

        $data[] = "" . (str_repeat('-', 100)) . "\n";
        $time = sprintf($this->_print_format, (microtime(true) - $this->_star[0]) * 1000);
        $memo = sprintf($this->_print_format, (memory_get_usage() - $this->_star[1]) / 1024);
        $total = sprintf($this->_print_format, (memory_get_usage()) / 1024);
        $data[] = "  {$time}\t{$memo}\t{$total}\t\n```\n";

        if (is_array($this->_mysql)) {
            $slow = [];
            foreach ($this->_mysql as $i => $sql) {
                if (intval($sql['wait']) > 20) {
                    $slow[] = $i;
                }
            }
            $data[] = "\n## Mysql 顺序：\n";
            $data[] = " - 当前共执行MYSQL：\t" . count($this->_mysql) . " 次\n";
            if (!empty($slow)) $data[] = " - 超过20Ms的语句有：\t" . implode(',', $slow) . "\n";
            $data[] = "```\n" . print_r($this->_mysql, true) . "\n```";
        }

        if ($this->_showPost and $method === 'POST') {
            $data[] = "\n## Post原始数据：\n```\n" . file_get_contents("php://input") . "\n```\n";
        }

        if ($this->_showHtml) {
            if (!_CLI) $data[] = "\n## 页面实际响应： \n```\n" . ob_get_contents() . "\n```\n";
            $data[] = "\n## HTML：\n```\n" . $response->_display_Result . "\n```\n";
        }
        if ($this->_showServer) {
            $data['_SERVER'] = "\n## _SERVER\n```\n:" . print_r($_SERVER, true) . "\n```\n";
        }
        $data[] = "\n";
        file_put_contents($filename, $data, LOCK_EX);
    }

    public function star($pre = null)
    {
        $this->_run = true;
        $pre = $pre ?: debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0];
        $this->relay('STAR BY HANDer', $pre);//创建一个结束点
    }

    public function post(bool $val)
    {
        $this->_showPost = $val;
        return $this;
    }

    public function server(bool $val)
    {
        $this->_showServer = $val;
        return $this;
    }

    public function disable()
    {
        $this->_run = false;
    }

    public function stop($pre = null)
    {
        if (!$this->_run) return;
        if (!empty($this->_node)) {
            $pre = $pre ?: debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0];
            $this->relay('STOP BY HANDer', $pre);//创建一个结束点
        }
        $this->_run = null;
    }

    public function __set($name, $value)
    {
        $this->_value[$name] = $value;
    }

    public function __get($name)
    {
        return $this->_value[$name] ?? null;
    }

    public function mysql_log($val)
    {
        if ($this->_run === false or is_null($this->_mysql)) return;
        $this->_mysql[] = $val;
    }

    /**
     * 创建一个debug点
     * @param $msg
     * @param null $prev 调用的位置，若是通过中间件调用，请在调用此函数时提供下面的内容：
     *
     * $pre=debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0];
     *
     */
    public function relay($msg, array $prev = null)
    {
        if ($this->_run === false) return $this;
        $prev = is_null($prev) ? debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0] : $prev;
        if (isset($prev['file'])) {
            $file = substr($prev['file'], strlen(_ROOT)) . " [{$prev['line']}]";
        } else {
            $file = null;
        }
        if (is_array($msg)) $msg = "\n" . print_r($msg, true);

        $this->_node_len = max(iconv_strlen($msg), $this->_node_len);
        $nowMemo = memory_get_usage();
        $time = sprintf($this->_print_format, (microtime(true) - $this->prevTime) * 1000);
        $memo = sprintf($this->_print_format, ($nowMemo - $this->memory) / 1024);
        $now = sprintf($this->_print_format, ($nowMemo) / 1024);
        $this->prevTime = microtime(true);
        $this->memory = $nowMemo;
        $this->_node[] = ['t' => $time, 'm' => $memo, 'n' => $now, 'g' => $msg, 'f' => $file];
        return $this;
    }

    private $path;
    private $file;
    private $fileName;
    private $error;

    public function path($path)
    {
        $this->path = $path;
        return $this;
    }

    public function file($file)
    {
        $this->file = $file;
        return $this;
    }

    public function error()
    {
        $this->error = true;
        return $this->filename();
    }

    public function filename($file = null)
    {
        if (is_string($file)) {
            $this->file = $file;
            $this->fileName = null;
        }
        if (!is_null($this->fileName)) return $this->fileName;
        list($s, $c) = explode('.', microtime(true) . '.0');
        $file = $this->file ?: (date('H_i_s', $s) . "_{$s}_{$c}_" . mt_rand(100, 999));
        if ($this->error) $file .= '_Error';
        $this->fileName = root($this->_log_path, true) . date('Y-m-d', $s) . '/' . ($this->path ?: _MODULE) . '/' . $file . '.md';
        mk_dir($this->fileName);
        return $this->fileName;
    }


}